package com.lagou.edu.mvcframework.servlet;

import com.lagou.edu.mvcframework.annotations.LagouAutowired;
import com.lagou.edu.mvcframework.annotations.LagouController;
import com.lagou.edu.mvcframework.annotations.LagouRequestMapping;
import com.lagou.edu.mvcframework.annotations.LagouService;
import com.lagou.edu.mvcframework.pojo.Handler;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.beans.Introspector;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.*;
import java.util.regex.Pattern;

public class LgDispatcherServlet extends HttpServlet {

    private final List<Class<?>> beanClasses = new ArrayList<>();
    private final Map<String, Object> ioc = new HashMap<>();
    private final List<Handler> handlers = new ArrayList<>();

    @Override
    public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        doPost(req, resp);
    }

    @Override
    public void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        Optional<Handler> handlerOpt = getHandler(req);
        if (!handlerOpt.isPresent()) {
            resp.getWriter().write("404 not found");
            return;
        }
        Handler handler = handlerOpt.get();
        Parameter[] parameters = handler.getMethod().getParameters();
        Object[] arg = new Object[parameters.length];
        Map<String, String[]> parameterMap = req.getParameterMap();

        for (Map.Entry<String, String[]> param : parameterMap.entrySet()) {
            String value = String.join(",", param.getValue());
            if (handler.getParamIndexMapping().containsKey(param.getKey())) {
                Integer index = handler.getParamIndexMapping().get(param.getKey());
                arg[index] = value;
            }
        }

        int requestIndex = handler.getParamIndexMapping().get(HttpServletRequest.class.getSimpleName());
        arg[requestIndex] = req;

        int responseIndex = handler.getParamIndexMapping().get(HttpServletResponse.class.getSimpleName());
        arg[responseIndex] = resp;

        try {
            handler.getMethod().invoke(handler.getController(), arg);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }

    public Optional<Handler> getHandler(HttpServletRequest request) {
        String url = request.getRequestURI();
        Optional<Handler> handler = this.handlers.stream()
                .filter(h -> h.getUrl().matcher(url).matches())
                .findFirst();
        return handler;
    }

    @Override
    public void init() throws ServletException {
        ServletConfig servletConfig = super.getServletConfig();
        String clc = servletConfig.getInitParameter("contextLoaderConfiguration");
        try {
            Properties config = loadConfig(clc);
            String basePackage = config.getProperty("scanPackage");
            doScan(basePackage);
            doCreateBean();
            doAutowired();
            initHandlerMapping();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void initHandlerMapping() {
        for (Map.Entry<String, Object> entry : ioc.entrySet()) {
            Object bean = entry.getValue();
            Class<?> beanClass = bean.getClass();
            if (beanClass.isAnnotationPresent(LagouController.class)) {
                String baseUrl = "";
                LagouRequestMapping rm = beanClass.getAnnotation(LagouRequestMapping.class);
                if (rm != null) {
                    baseUrl += rm.value();
                }
                for (Method method : beanClass.getDeclaredMethods()) {
                    LagouRequestMapping mrm = method.getAnnotation(LagouRequestMapping.class);
                    String methodUrl = mrm.value();
                    handlers.add(new Handler(bean, method, Pattern.compile(baseUrl + methodUrl)));
                }
            }
        }
    }

    private void doAutowired() throws IllegalAccessException {
        for (Map.Entry<String, Object> entry : ioc.entrySet()) {
            Object bean = entry.getValue();
            for (Field field : bean.getClass().getDeclaredFields()) {
                if (field.isAnnotationPresent(LagouAutowired.class)) {
                    field.setAccessible(true);
                    LagouAutowired autowired = field.getAnnotation(LagouAutowired.class);
                    String value = "".equals(autowired.value()) ? field.getType().getName() : autowired.value();
                    field.set(bean, ioc.get(value));
                }
            }
        }
    }

    private void doCreateBean() throws ClassNotFoundException, InstantiationException, IllegalAccessException {
        for (Class<?> clazz : this.beanClasses) {
            String defaultName = Introspector.decapitalize(clazz.getSimpleName());
            if (clazz.isAnnotationPresent(LagouController.class)) {
                ioc.put(defaultName, clazz.newInstance());
            } else if (clazz.isAnnotationPresent(LagouService.class)) {
                LagouService service = clazz.getAnnotation(LagouService.class);
                String value = service.value();
                Object bean = clazz.newInstance();
                ioc.put("".equals(value.trim()) ? defaultName : value, bean);
                for (Class<?> itf : clazz.getInterfaces()) {
                    ioc.put(itf.getName(), bean);
                }
                Arrays.stream(clazz.getInterfaces()).forEach(i -> ioc.put(i.getName(), bean));
            }
        }
    }

    private void doScan(String basePackage) throws ClassNotFoundException {
        String basePackagePath = Thread.currentThread()
                .getContextClassLoader()
                .getResource(basePackage.replace(".", "/"))
                .getPath();
        File baseFile = new File(basePackagePath);
        for (File file : baseFile.listFiles()) {
            if (file.isDirectory()) {
                doScan(basePackage + "." + file.getName());
            } else {
                if (file.getName().endsWith(".class")) {
                    String name = basePackage + "." + file.getName().replace(".class", "");
                    this.beanClasses.add(Class.forName(name));
                }
            }
        }
    }

    private Properties loadConfig(String clc) throws IOException {
        Properties properties = new Properties();
        try (InputStream ias = Thread.currentThread().getContextClassLoader().getResourceAsStream(clc)) {
            properties.load(ias);
        }
        return properties;
    }

}
